/**
 * X兼容Y：X（目标类型）= Y（源类型）
 * */

let s: string = "a";
// s = null; // 需要将 tsconfig.json 中的 `strictNullChecks` 设置为false

// 接口兼容性
interface X {
  a: any;
  b: any;
}

interface Y {
  a: any;
  b: any;
  c: any;
}

let x: X = { a: 1, b: 2 };
let y: Y = { a: 1, b: 2, c: 3 };
// 我们看一下，x和y可以相互赋值吗
x = y; // y可以赋值给x
// y = x; // x不可以赋值给y

// 函数兼容性
type Handler = (a: number, b: number) => void;
function hof(handler: Handler) {
  return handler;
}

// 1)参数个数
let handler1 = (a: number) => {};
// 那么接下来看看能不能把handler1做为参数传递给hof这个高阶函数
hof(handler1); // 我们看是可以的，因为它只有一个参数

// 接下来呢我们来定义一个有三个参数的函数
let handler2 = (a: number, b: number, c: number) => {};
// 那么下面我们看 handler2 这个函数能不能作为参数传递给这个高阶函数呢
hof(handler2); // 我们看是不可以的，因为参数是三个而目标函数呢有两个参数

// 可选参数和剩余参数

// 那比如说呢：这里我们定义了三个函数它们分别具有固定参数、可选参数和剩余参数，那它们之间的兼容性是怎样的呢
let a = (p1: number, p2: number) => {};
let b = (p1?: number, p2?: number) => {};
let c = (...args: number[]) => {};

// 1. 第一个原则呢就是：固定参数是可以兼容可选参数和剩余参数的
a = b; // a可以兼容b
a = c; // a也可以兼容c

// 2. 第二个原则呢就是：可选参数是不兼容固定参数和剩余参数的
b = c; // 这里需要把 "strictNullChecks": true 设置为true，b是不兼容c的
b = a; // b也不兼容a
// 那么这时候呢我们可以通过关闭一个选项来实现它们之间的兼容，也就是这个 `strictNullChecks`

// 3. 那么第三个呢就是：剩余参数可以兼容固定参数和可选参数
c = a; // c可以兼容a
c = b; // c也可以兼容b

// 2.) 参数类型
let handler3 = (a: string) => {};
hof(handler3); //那么handler3可以作为参数传递给 hof 这个高阶函数吗，我们看是不可以的，因为它的类型是不兼容的

// 如果是一个对象类型那么就比较复杂了

interface Point3D {
  x: number;
  y: number;
  z: number;
}

interface Point2D {
  x: number;
  y: number;
}
// 参数是接口类型
// 那么这两个函数呢，首先它们的参数个数是相同的，参数类型呢都是对象
let p3d = (point: Point3D) => {};
let p2d = (point: Point2D) => {};

// 那么 p3d和p2d 它们之间的兼容性是怎样的呢？我们来看一下
p3d = p2d; // p3d是兼容p2d的
p2d = p3d; //p2d是不兼容p3d的

// 这里呢我们需要关注一下两个函数的参数它们都是对象，只不过呢p3d的对象中有3个成员，p2d中呢对象有两个成员，
// 这里呢就是成员个数多的要兼容成员个数少的，
// 这个结论呢与我们之前接口之间兼容性的结论正好相反，两个接口之间兼容呢要求成员少的兼容成员多的，
// 那么这里呢正好反过来了，非常容易混淆，这里呢交给你一个办法：
// 不要把这个对象看成一个整体的对象，你可以把它拆分成三个参数，这样呢就是参数多的会兼容参数少的，这个呢就与我们之前的结论一致了。

// 如果要p2d兼容p3d呢，也是可以做到的，我们需要关闭 "strictNullChecks": false 设置为false

// 返回值类型

let f = () => ({ name: "Alice" });
let g = () => ({ name: "Alice", location: "Beijing" });

f = g; // 那么f呢就可以兼容g
g = f; // 反过来呢g是不兼容f的，因为f的返回值类型是g返回值类型的子类型
// 那么同样呢这里也是成员少的兼容成员多的与鸭式辩型法式一样的

// 函数重载

// 在这个列表中呢我们定义了两个重载函数和一个函数的具体实现
function overload(a: number, b: number): number;
function overload(a: string, b: string): number; // 这里呢列表中的函数呢就是目标函数
function overload(a: any, b: any): any {} // 而具体的实现呢就是源函数

// 程序在运行的时候呢编译器会查找这个重载的列表然后使用第一个匹配的定义来执行下面的函数
// 所以呢在重载列表中目标函数的参数要多余源函数的参数，而且呢返回值类型也要符合相应的要求

// 比如说呢我们具体实现的时候我们给它增加一个参数，那么这样是不兼容的
// 因为这个实现的参数个数多余目标函数的参数个数
// function overload(a: any, b: any, c: any): any {}

// 比如说我们再把返回值删掉
// 我们看也是不兼容的，这是因为返回值类型是不兼容的
// function overload(a: any, b: any) {}

// 枚举兼容性
enum Fruit {
  Apple,
  Banana,
}
enum Color {
  Red,
  Yellow,
}

// 枚举类型和数值类型是完全可以兼容的
let fruit: Fruit.Apple = 3; // 它的类型是一个枚举类型
let no: number = Fruit.Apple;
// 所以我们看到枚举和number是可以完全相互兼容的

// 那枚举之间呢是完全不兼容的
let color: Color.red = Fruit.Apple; // 我们看到color和fruit是完全不兼容的

// 类兼容性

// 类的兼容性和接口呢比较相似，它们也是只比较结构，那么这里需要注意一点：
// 就是在比较两个类是否兼容的时候呢静态成员和构造函数是不参与比较的，
// 那么如果两个类具有相同的实例成员，那么它们的实例就可以完全相互兼容
class A {
  constructor(p: number, q: number) {} // 构造函数不参与比较
  id: number = 1;
  private name: string = ""; // 私有成员
}
class B {
  static s = 1; // 静态成员不参与比较
  constructor(p: number) {}
  id: number = 2;
  private name: string = ""; // 私有成员
}
let aa = new A(1, 2);
let bb = new B(1);

// 这里呢我们定义了两个类A和B，然后分别呢创建了两个实例。

// 我们看看这两个实例是不是兼容的
// aa = bb; // 【没有私有成员】
// bb = aa; // 可以看到两个实例是完全兼容的，因为它们都具有一个实例属性 id，而构造函数呢和静态成员是不作为比较的

// 那么如果这个类中含有一个私有成员呢？

aa = bb; // 如果类中呢含有私有成员，那么这两个类就不兼容了，这个时候呢只有父类和子类之间是可以相互兼容的
bb = aa;

// 那接下来呢我们在来定义一个 A 的子类
class C extends A {}

let cc = new C(1, 2);
aa = cc;
cc = aa; // 我们看父类和子类的实例是可以完全相互兼容的

// 泛型兼容性

// 我们先看一个泛型接口，这个接口呢没有任何的成员
interface Empty<T>{}
// 我们定义了两个变量，这两个变量呢都是这个接口类型，只不过具体传的类型参数是不同的
let obj1: Empty<number> = {};
let obj2: Empty<string> = {};

// 我们在看这两个变量是不是兼容
obj1 = obj2; // 我们可以看到这两个变量是兼容的，这是因为呢这个泛型接口中呢没有任何的成员

// 那么接下来呢，我们给它加一个成员
interface Empty1<T>{
  value: T;
}
let obj11: Empty1<number> = {value: 2};
let obj22: Empty1<string> = {value: '3'};

obj11 = obj22; // 那么这个时候呢两个变量就不兼容了


// 泛型函数

// 这里呢我们定义了两个完全相同的泛型函数
let log1 = <T>(x: T): T => {
  console.log('x');
  return x;
}
let log2 = <U>(y: U): U => {
  console.log('y');
  return y;
}

// 那么它们之间是不是兼容的呢
log1 = log2; // 也是完全兼容的

export {};
